import * as odbc from "odbc";
import { ColumnConfig } from "./config";

type ColumnsConfig = { [name: string]: ColumnConfig };

export class Database {
    private connectionString: string;
    private connection: odbc.Connection | null = null;

    constructor(connectionString: string) {
        this.connectionString = connectionString;
    }

    private async getConnection(): Promise<odbc.Connection> {
        if (!this.connection) {
            this.connection = await this.createConnection();
        }
        return this.connection;
    }

    private getCreateTableSql(tableName: string, columnsConfigs: ColumnsConfig): string {
        let sql = `CREATE TABLE ${tableName} (`;

        let count = Object.keys(columnsConfigs).length;
        let index = 0;
        let primaryKeys = [];

        for (let columnName in columnsConfigs) {
            const columnConfig = columnsConfigs[columnName];
            if (columnConfig.primary) {
                primaryKeys.push(columnName);
            }

            sql = `${sql}\n\t${columnName} ${columnConfig.type || 'string'}`;
            if (index < count - 1) {
                sql = sql + ',';
            }

            index = index + 1;
        }

        if (primaryKeys.length > 0) {
            sql = `${sql},\n\tPRIMARY KEY (${primaryKeys.join(', ')})`;
        }

        sql = `${sql}\n)\nSTORED AS KUDU`;

        return sql;
    }

    private async getAlterTableSql(tableName: string, columnsConfigs: ColumnsConfig): Promise<string | null> {
        let existsColumnNames = await this.getColumnNames(tableName);
        let notExistsColumnNames = Object.keys(columnsConfigs).filter(name => !existsColumnNames.includes(name));
        if (notExistsColumnNames.length <= 0)
            return null;

        let sql = `ALTER TABLE ${tableName} ADD COLUMNS (`;
        for (let i = 0; i < notExistsColumnNames.length; i++) {
            const columnName = notExistsColumnNames[i];
            const columnConfig = columnsConfigs[columnName];
            sql = `${sql}\n\t${columnName} ${columnConfig.type || 'string'}`;
            if (i < notExistsColumnNames.length - 1) {
                sql = sql + ',';
            }
        }
        sql = `${sql}\n)`;
        return sql;
    }

    public async createConnection(): Promise<odbc.Connection> {
        const connection = await odbc.connect(this.connectionString);
        return connection;
    }

    public async getTableNames(): Promise<string[]> {
        type Row = { name: string };
        const connection = await this.getConnection();
        const result = await connection.query<Row>("show tables");
        const tables = result.map((row: Row) => row.name);
        return tables;
    }

    public async getColumnNames(tableName: string): Promise<string[]> {
        type Row = { name: string, Column: string };
        const connection = await this.getConnection();
        const result = await connection.query<Row>(`SHOW COLUMN STATS ${tableName}`);
        const columns = result.map((row: Row) => row.name || row.Column);
        return columns;
    }

    public async createTable(tableName: string, columnConfigs: { [name: string]: ColumnConfig }): Promise<void> {
        let sql = this.getCreateTableSql(tableName, columnConfigs);
        const connection = await this.getConnection();
        console.log(sql);
        await connection.query(sql);
    }

    public async alterTable(tableName: string, columnConfigs: { [name: string]: ColumnConfig }): Promise<void> {
        let sql = await this.getAlterTableSql(tableName, columnConfigs);
        if (!sql) {
            return;
        }

        const connection = await this.getConnection();
        console.log(sql);
        await connection.query(sql);
    }

    public async dropTable(tableName: string): Promise<void> {
        const connection = await this.getConnection();
        let sql = `DROP TABLE IF EXISTS ${tableName}`;
        console.log(sql);
        await connection.query(sql);
    }

    public async close() {
        if (this.connection)
            await this.connection.close();
    }

}
